At this point, it's time to deal with an interesting
aspect of our application's architecture: the representation and use of
tools corresponding to the buttons we put in the GUI. We'll define a
number of tool classes, each conforming to a particular protocol so that
our controller can talk to them all. Our controller will keep a pointer
to the active tool, based on the user's selection, and will pass all
touch events along to the active tool, which will deal with the events
in whatever way is appropriate for it. It will also pass along the drawTemporary
message from the view, so that the tool can draw a representation of
the work in progress if it's a multistage creation action. Our
controller doesn't need to know any specifics about how a tool
interprets events, defines drawing operations, or anything else.
In addition, DudelViewController
will maintain state information about other potential user-selected
values, such as the current fill color (used to fill the inside of the
shape that's being drawn) and the current stroke color (used to draw the
edge of the shape). That information will be available for the active
tool to access when it's doing its temporary drawing and when it's
creating a completed drawing operation to give to the view. For now,
we're not going to provide any mechanism for setting the fill and stroke
colors. We'll just give them predefined values.
The final piece of functionality that DudelView will include is responding to presses of the UIBarButtonItems in the toolbar, which will result in a new tool being set as the active tool. To get started, add a few lines to DudelViewController.h:
// DudelViewController.h
#import <UIKit/UIKit.h>
#import "Tool.h"
#import "DudelView.h"
@interface DudelViewController : UIViewController <ToolDelegate, DudelViewDelegate> {
id <Tool> currentTool;
IBOutlet DudelView *dudelView;
IBOutlet UIBarButtonItem *freehandButton;
IBOutlet UIBarButtonItem *ellipseButton;
IBOutlet UIBarButtonItem *rectangleButton;
IBOutlet UIBarButtonItem *lineButton;
IBOutlet UIBarButtonItem *pencilButton;
UIColor *strokeColor;
UIColor *fillColor;
CGFloat strokeWidth;
}
@property (retain, nonatomic) id <Tool> currentTool;
@property (retain, nonatomic) UIColor *strokeColor;
@property (retain, nonatomic) UIColor *fillColor;
@property (assign, nonatomic) CGFloat strokeWidth;
- (IBAction)touchFreehandItem:(id)sender;
- (IBAction)touchEllipseItem:(id)sender;
- (IBAction)touchRectangleItem:(id)sender;
- (IBAction)touchLineItem:(id)sender;
- (IBAction)touchPencilItem:(id)sender;
@end
Next, move to DudelViewController.m.
This is where the real changes take place. Among other things, this
file contains implementations for all the action methods we defined in
the header file. These are all empty for now, but will be filled in
later as we cover each new tool. It also defines a few utility methods
for internal use, for taking care of some repetitive tasks that we'll
need to do for each of those action methods.
// DudelViewController.m
#import "DudelViewController.h"
#import "DudelView.h"
@implementation DudelViewController
@synthesize currentTool, fillColor, strokeColor, strokeWidth;
- (void)deselectAllToolButtons {
[textButton setImage:[UIImage imageNamed:@"button_text.png"]];
[freehandButton setImage:[UIImage imageNamed:@"button_bezier.png"]];
[ellipseButton setImage:[UIImage imageNamed:@"button_ellipse.png"]];
[rectangleButton setImage:[UIImage imageNamed:@"button_rectangle.png"]];
[lineButton setImage:[UIImage imageNamed:@"button_line.png"]];
[pencilButton setImage:[UIImage imageNamed:@"button_cdots.png"]];
}
- (void)setCurrentTool:(id <Tool>)t {
[currentTool deactivate];
if (t != currentTool) {
[currentTool release];
currentTool = [t retain];
currentTool.delegate = self;
[self deselectAllToolButtons];
}
[currentTool activate];
[dudelView setNeedsDisplay];
}
- (IBAction)touchFreehandItem:(id)sender {
}
- (IBAction)touchEllipseItem:(id)sender {
}
- (IBAction)touchRectangleItem:(id)sender {
}
- (IBAction)touchLineItem:(id)sender {
}
- (IBAction)touchPencilItem:(id)sender {
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[currentTool touchesBegan:touches withEvent:event];
[dudelView setNeedsDisplay];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[currentTool touchesCancelled:touches withEvent:event];
[dudelView setNeedsDisplay];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[currentTool touchesEnded:touches withEvent:event];
[dudelView setNeedsDisplay];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[currentTool touchesMoved:touches withEvent:event];
[dudelView setNeedsDisplay];
}
- (void)addDrawable:(id <Drawable>)d {
[dudelView.drawables addObject:d];
[dudelView setNeedsDisplay];
}
- (UIView *)viewForUseWithTool:(id <Tool>)t {
return self.view;
}
- (void)drawTemporary {
[self.currentTool drawTemporary];
}
- (void)viewDidLoad {
[super viewDidLoad];
self.fillColor = [UIColor lightGrayColor];
self.strokeColor = [UIColor blackColor];
self.strokeWidth = 2.0;
}
// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation {
return YES;
}
- (void)dealloc {
self.currentTool = nil;
self.fillColor = nil;
self.strokeColor = nil;
[super dealloc];
}
@end
That code also refers to a file called Tool.h, and the Tool
protocol it defines. That's the protocol with which the controller
communicates with the selected tool. Create a new protocol header file
in your project, name it Tool.h, and fill it with this:
// Tool.h
#import <UIKit/UIKit.h>
@protocol ToolDelegate;
@protocol Drawable;
@protocol Tool <NSObject>
@property (assign, nonatomic) id <ToolDelegate> delegate;
- (void)activate;
- (void)deactivate;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)drawTemporary;
@end
@protocol ToolDelegate
- (void)addDrawable:(id <Drawable>)d;
- (UIView *)viewForUseWithTool:(id <Tool>)t;
- (UIColor *)strokeColor;
- (UIColor *)fillColor;
@end
This also defines the ToolDelegate protocol, with which each tool can communicate back to the controller.
At this point, you should be
able to build and run your app. You'll still wind up with a blank slate
that doesn't do anything, but doing so will at least verify that you're
on track.
Next, let's tackle the tools.